import { CacheService } from '@app/cache';
import { BaseService } from '@app/db/base/base.service';
import { CardRechargeOrder } from '@app/db/entities/CardRechargeOrder';
import { ChargingCard } from '@app/db/entities/ChargingCard';
import { RechargeOption } from '@app/db/entities/RechargeOption';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, FindOptionsWhere, LessThan, Repository } from 'typeorm';
import { WechatService } from '../wechat/wechat.service';
import { ChargingCardLog } from '@app/db/entities/ChargingCardLog';
import { AlipayService } from '../alipay/alipay.service';

@Injectable()
export class ChargingCardService  extends BaseService<ChargingCard> {
    constructor(
        @InjectRepository(ChargingCard) protected repository:Repository<ChargingCard>,
        @InjectRepository(CardRechargeOrder) protected cardRechargeOrder:Repository<CardRechargeOrder>,
        private cache:CacheService,
        @InjectRepository(RechargeOption) protected rechargeOption:Repository<RechargeOption>,
        protected dataSource: DataSource,
        private readonly wechat:WechatService,
        private readonly alipay:AlipayService
    ){
        super()
        this.primaryKey = 'id'
    }

    async cards(userId:number) {
        let where:FindOptionsWhere<ChargingCard> = { userId }
        let rows:ChargingCard[] = await this.repository.find({
            where,
            select : ['id', 'cardNo', 'activeTime', 'amount', 'rewardAmount'], 
            order : {id : 'DESC'}
        })

        return rows
    }

    async bind(cardNo:string, userId:number) {
        const lockKey = 'a:card:' + cardNo
        const lock = await this.cache.set(lockKey, '1', 10, 'NX')
        if(!lock) return -1

        const card = await this.findOne({cardNo : cardNo}, ['status', 'id'])
        if(!card || card.status != 0) return -1

        const r = await this.update({id : card.id}, {status : 1, userId, activeTime : Math.floor(Date.now() / 1000)})

        return r > 0
    }

    async unbind(id:number, userId:number) {
        const card = await this.findOne({id : id}, ['status', 'userId'])
        if(!card || card.userId != userId) return false

        const r = await this.update({id : id}, {status : 0, activeTime : 0, userId : 0})

        return r > 0
    }

    private generateOrderNo(userId:number) {
        const date = new Date()
        const time = Math.floor(date.getTime() / 1000) % 1000000
        const year = date.getFullYear() % 100
        const month = String(date.getMonth() + 1).padStart(2, '0')
        const day = String(date.getDate()).padStart(2, '0')
        return year + month + day + time + String(userId).padStart(10, '0')
    }


    async rechargePay(optionId:number, cardId:number, userId:number, clientSource:number, isWx:boolean, openid:string) {
        const option = await this.rechargeOption.findOne({
            where : {id : optionId},
            select : ['amount', 'reward']
        })

        if(!option) return null

        let amount = Number(option.amount)
        //let rewardAmount = Number(option.reward)

        

        let time = Math.floor(Date.now() / 1000)
        const orderNo = this.generateOrderNo(userId)
        let order:{[key in keyof CardRechargeOrder]? : CardRechargeOrder[key]} = {
            orderNo : orderNo,
            amount : option.amount,
            rewardAmount : option.reward,
            payChannel : clientSource,
            userId : userId,
            cardId,
            createTime : time,
            status : 0
        }

        try {
            const r = await this.cardRechargeOrder.insert(order)
            if(!r.raw || r.raw.affectedRows < 1) {
                return null
            }

            const orderId = r.raw.insertId

            const notifyUrl = 'chargingCard/recharge/notify/'

            if(isWx) {
                return this.wechat.pay(amount, orderNo, openid, '充电卡充值', notifyUrl + 'wx/' + orderId)
            }else {
                return this.alipay.createTrade(amount, orderNo, openid, '充电卡充值', notifyUrl + 'ali/' + orderId)
            }
            

        } catch (error) {
            
        }

        return null
        
    }

    async rechargeNotify(orderId:number, data, isWx:boolean) {
        const lockKey= 'cc:n:' + orderId
        const lock = await this.cache.set(lockKey, '1', 20, 'NX')
        if(!lock) return false

        const order = await this.cardRechargeOrder.findOne({
            where : {id : orderId},
            select : ['status', 'userId', 'amount', 'rewardAmount', 'orderNo', 'cardId']
        })

        if(order == null || order.status == 1) return true

        const amount = Number(order.amount)
        const rewardAmount = Number(order.rewardAmount)
        const time = Math.floor(Date.now() / 1000)

        let tradeNo = ''

        if(isWx) {
            try {
                const res = await this.wechat.decipher_gcm(data.ciphertext, data.associated_data, data.nonce)

                if(res.trade_state != 'SUCCESS' || res.out_trade_no != order.orderNo || res.amount.total != (amount * 100)) return

                tradeNo = res.transaction_id

            } catch (error) {
                this.cache.del(lockKey)
                console.log(error)
                return false
            } 
           
        }else { //支付宝

            try {
                if(!this.alipay.checkNotifySign(data)) return false

                if(data.out_trade_no != order.orderNo || data.total_amount != amount) return false

                tradeNo = data.trade_no

            } catch (error) {
                this.cache.del(lockKey)
                console.log(error)
                return false
            }

        }

        const queryRunner = await this.dataSource.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {

            let card = await queryRunner.manager.findOne<ChargingCard>(ChargingCard, {
                where : {id : order.cardId},
                select : ['amount', 'rewardAmount'],
                lock : {
                    mode : 'pessimistic_read'
                }
            })

            //卡钱包更新
            let updateData:{amount:string, rewardAmount?:string} = {
                amount : (Number(card.amount) + amount).toFixed(2)
            }

            //钱包记录
            const log:{[key in keyof ChargingCardLog]? : ChargingCardLog[key]} = {
                amount : order.amount,
                beforeAmount : card.amount,
                beforeRewardAmount : card.rewardAmount,
                afterAmount : updateData.amount,
                userId : order.userId,
                cardId : order.cardId,
                createTime : time,
                type : 1
            }

            if(rewardAmount > 0) {
                log.rewardAmount = order.rewardAmount
                log.afterRewardAmount = updateData.rewardAmount = (Number(card.rewardAmount) + rewardAmount).toFixed(2) 
            }

            //更改卡余额
            const r1 = await queryRunner.manager.update<ChargingCard>(ChargingCard, { id : order.cardId }, updateData)
            if(r1.affected < 1) {
                await queryRunner.rollbackTransaction();
                return false
            }

            //加入钱包记录
            const r2 = await queryRunner.manager.insert<ChargingCardLog>(ChargingCardLog, log)
            if(!r2.raw || r2.raw.affectedRows < 1) {
                await queryRunner.rollbackTransaction();
                return false
            }

            //更改订单状态
            const r3 = await queryRunner.manager.update<CardRechargeOrder>(CardRechargeOrder, { id : orderId }, {payTime : time, status : 1, tradeNo : tradeNo})
            if(r3.affected < 1) {
                await queryRunner.rollbackTransaction();
                return false
            }

            await queryRunner.commitTransaction()

            return true

            
        } catch (error) {
            console.log(error)
            await queryRunner.rollbackTransaction();

            return false
        } finally {
            this.cache.del(lockKey)
            await queryRunner.release();
        }
    }

    async rechargeRecord(query:Pagination & {cardId:number}) {
        let where:FindOptionsWhere<CardRechargeOrder> = { cardId : query.cardId }
        const lastId = query.lastKey || 0
        const pageSize = query.pageSize || 20

        if(lastId > 0) {
            where.id = LessThan(lastId)
        }

        let rows:CardRechargeOrder[] = await this.cardRechargeOrder.find({
            where,
            select : ['id', 'orderNo', 'amount', 'rewardAmount', 'createTime', 'status'], 
            skip : 0, 
            take : pageSize, 
            order : {id : 'DESC'}
        })

        return rows
    }
}
